package cn.xeblog.plugin.game.sudoku;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.xeblog.commons.util.ThreadUtils;
import cn.xeblog.plugin.game.sudoku.algorithm.Generator;
import cn.xeblog.plugin.game.sudoku.algorithm.Grid;
import cn.xeblog.plugin.game.sudoku.algorithm.Solver;
import cn.xeblog.plugin.game.sudoku.enums.Level;
import cn.xeblog.plugin.game.sudoku.enums.PanelSize;
import cn.xeblog.plugin.game.sudoku.enums.RealTimeTip;
import cn.xeblog.plugin.game.sudoku.enums.Theme;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/**
 * 数独UI页面
 *
 * @author ☆程序员鼓励师☆
 * @date 2022/8/12 11:34
 */
public class SudokuGui extends JPanel implements ActionListener {

  /**
   * 主题
   */
  private final Theme theme;
  /**
   * 题面
   */
  private int[][] puzzleInts;
  /**
   * 题解
   */
  private int[][] solutionInts;
  /**
   * 用户解题数据，即检查网格
   */
  private final Grid checkGrid = Grid.emptyGrid();

  /**
   * 输入字符串最大长度
   */
  private final int maxLength = 1;

  /**
   * 缓存下所有格子的初始化背景色
   */
  private final Map<String, Color> colorMap = new HashMap<>(81);

  /**
   * 数据模块
   */
  private final JTextField[][] chessBoard = new JTextField[9][9];

  /**
   * 数独界面
   *
   * @param level       级别
   * @param panelSize   面板大小
   * @param realTimeTip 实时提示
   * @param theme       主题
   */
  public SudokuGui(Level level, PanelSize panelSize, RealTimeTip realTimeTip, Theme theme) {
    this.theme = theme;
    // 初始化数据
    initData(level);
    // 初始化面板
    initJPanel(panelSize, realTimeTip);
    // 刷新界面
    repaint();
  }

  /**
   * 初始化数据
   */
  private void initData(Level level) {
    // 生成题面
    puzzleInts = new Generator().generate(level.getBlank()).toArray();
  }

  /**
   * 解密
   */
  public void doSolution(List<JButton> buttonList) {
    TimeInterval timer = DateUtil.timer();

    // 生成一组题解
    // 将题面将化为网络对象
    Grid grid = Grid.of(puzzleInts);
    // 解题
    new Solver().solve(grid);
    // 题面的题解，即答案
    solutionInts = grid.toArray();

    //System.out.println("算法耗时[" + timer.interval() + "]ms");

    // 刷新按钮
    buttonList.forEach(b -> {
      // 解除禁用
      b.setEnabled(true);
      // 刷新界面
      b.repaint();
    });
  }

  /**
   * 初始化面板
   *
   * @param panelSize   面板大小
   * @param realTimeTip 实时提示
   */
  private void initJPanel(PanelSize panelSize, RealTimeTip realTimeTip) {
    // 设置此组件的首选大小
    this.setPreferredSize(new Dimension(panelSize.getTableWidth(), panelSize.getTableHeight()));
    // 设置为网格布局且可见
    this.setLayout(new GridLayout(9, 9));
    this.setVisible(true);

    // 遍历网格布局
    for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
        // 设置JTextField的内容
        JTextField jTextField = new JTextField();
        chessBoard[i][j] = jTextField;
        this.add(jTextField);

        // 文本框可见且设置文本框中字体样式且居中
        jTextField.setVisible(true);
        jTextField.setFont(new Font("", Font.BOLD, panelSize.getFontSize()));
        jTextField.setHorizontalAlignment(JTextField.CENTER);
        // 设置背景颜色
        setBackgroundColor(i, j);

        // 文本填入处理
        jTextField.setDocument(generatePlainDocument());

        // 实时检测
        if (realTimeTip.isEnabled()) {
          jTextField.addFocusListener(generateFocusListener(i, j));
        }

        // 谜题字体前景色
        if (puzzleInts[i][j] == 0) {
          jTextField.setForeground(theme.getPuzzleForeground());
        } else {
          // 解题字体前景色
          jTextField.setForeground(theme.getSolutionForeground());
          jTextField.setText(Integer.toString(puzzleInts[i][j]));
          // 设置是否可获得焦点
          jTextField.setFocusable(false);
        }

        colorMap.put(i + "-" + j, jTextField.getForeground());
      }
    }
  }

  /**
   * 文本填入处理 限制只允许输入一个数字，后填的数字覆盖前面的
   */
  private PlainDocument generatePlainDocument() {
    return new PlainDocument() {
      @Override
      public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
        // 为空，跳过
        if (StrUtil.isEmpty(str)) {
          return;
        }

        int length = 0;
        char[] s = str.toCharArray();
        // 过滤非数字
        for (int i = 0; i < s.length; i++) {
          if ((s[i] >= '0') && (s[i] <= '9')) {
            s[length++] = s[i];
          }
        }

        if (length > 0) {
          remove(0, getLength());
          // 直接截取过滤后的 maxLength 长度的字符
          String text = new String(s, 0, length).substring(0, maxLength);
          super.insertString(0, text, attr);
        }
      }
    };
  }

  /**
   * 设置背景颜色
   *
   * @param row 行
   * @param col 列
   */
  private void setBackgroundColor(int row, int col) {
    // 设置九宫格的背景颜色 贴近深色系统
    if ((row / 3 == 0 && col / 3 == 0) || (row / 3 == 1 && col / 3 == 1) || (row / 3 == 2 && col / 3 == 2) || (row / 3 == 0
        && col / 3 == 2) || (row / 3 == 2 && col / 3 == 0)) {
      // 将棋盘分成以9个3x3个单元格为一组，组成一个大的九宫格，其中四个角加中间部分设置为深色九宫格单元背景色
      chessBoard[row][col].setBackground(theme.getBackgroundColorUnit1());
    } else {
      // 将棋盘分成以9个3x3个单元格为一组，组成一个大的九宫格，其中四条边中间部分设置为浅色九宫格单元背景色
      chessBoard[row][col].setBackground(theme.getBackgroundColorUnit2());
    }
  }

  /**
   * 获取提交按钮
   */
  public JButton getCommitJButton() {

    JButton commit = new JButton("提交");
    // 提交按钮事件监听
    commit.addActionListener(e -> {
      refreshCheckInts();
      // 错误的数量,数据自检结果
      int errorCount = checkSelfValues(checkGrid);
      // 弹出提示
      if (errorCount > 0) {
        String failureInfo = "很遗憾，你填错了" + errorCount + "个格子！";
        JOptionPane.showMessageDialog(null, failureInfo, "挑战失败！", JOptionPane.INFORMATION_MESSAGE);
      } else {
        String successInfo = "干得漂亮！完全正确！";
        JOptionPane.showMessageDialog(null, successInfo, "挑战成功！", JOptionPane.INFORMATION_MESSAGE);
      }
    });
    return commit;
  }

  /**
   * 刷新用户解题数据，即 将当前网格中的数据放入检查网格中，刷新检查网格
   */
  private void refreshCheckInts() {
    // 遍历整个网格
    for (int row = 0; row < 9; row++) {
      for (int column = 0; column < 9; column++) {
        String text = chessBoard[row][column].getText();
        int defaultValue = -1;
        // 棋盘面板指定单元格不为空且不为0时将值放入至检查网格中
        if (StrUtil.isNotEmpty(text) && Integer.parseInt(text) != 0) {
          defaultValue = Integer.parseInt(text);
        }
        checkGrid.getCell(row, column).setValue(defaultValue);
      }
    }
  }

  /**
   * 获取提示按钮
   */
  public JButton getTipsJButton() {
    JButton tips = new JButton("提示");
    // 提示按钮监听事件
    tips.addActionListener(e -> {
      refreshCheckInts();
      int[][] checkInts = checkGrid.toArray();

      // 遍历单元格
      for (int row = 0; row < 9; row++) {
        for (int column = 0; column < 9; column++) {
          // 如果chessBoard内的文本与答案不相同，设置前景色且设置指定单元格正确值
          if (checkInts[row][column] != solutionInts[row][column]) {
            chessBoard[row][column].setForeground(theme.getTipForegroundResult());
            chessBoard[row][column].setText(Integer.toString(solutionInts[row][column]));
          }
          // 刷新背景色
          colorMap.put(row + "-" + column, chessBoard[row][column].getForeground());
        }
      }
    });
    return tips;
  }

  /**
   * 数据自检
   * <p>
   * 这个方法略微耗时 以后可以优化
   *
   * @param checkGrid 待检查网格
   * @return 错误的数量
   */
  private int checkSelfValues(Grid checkGrid) {
    int errorCount = 0;
    // 遍历单元格
    for (int row = 0; row < 9; row++) {
      for (int column = 0; column < 9; column++) {
        // 获取指定的单元格
        Grid.Cell cell = checkGrid.getCell(row, column);
        int checkValue = cell.getValue();

        // 如果是题面直接跳过
        if (checkValue == puzzleInts[row][column]) {
          continue;
        }

        // 将待检查的位置数据替换为0 假装正要填下 进行自检
        cell.setValue(0);
        // 值不能小于0或者值在同一行、列和框中存在且值与当前题解不相等，说明错误
        if (checkValue == -1 || !checkGrid.isValidValueForCell(cell, checkValue) && checkValue != solutionInts[row][column]) {
          errorCount++;
        }
        cell.setValue(checkValue);
      }
    }
    return errorCount;
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    // 发生操作时调用
  }

  /**
   * 获取冲突集合，主要是判断是否合规
   *
   * @param board  数据集
   * @param row    行索引
   * @param column 列索引
   * @param val    校验待的数
   */
  public Set<String> getConflictSet(int[][] board, int row, int column, int val) {
    Set<String> conflictSet = new HashSet<>();
    // 列校验
    for (int i = 0; i < 9; i++) {
      if (board[i][column] == val) {
        conflictSet.add(i + "-" + column);
      }
    }
    // 行校验
    for (int j = 0; j < 9; j++) {
      if (board[row][j] == val) {
        conflictSet.add(row + "-" + j);
      }
    }
    // 3 * 3 单元校验
    for (int x = row / 3 * 3, i = x; i < x + 3; i++) {
      for (int y = column / 3 * 3, j = y; j < y + 3; j++) {
        if (board[i][j] == val) {
          conflictSet.add(i + "-" + j);
        }
      }
    }

    // 不标记当前位置
    if (conflictSet.size() == 1) {
      conflictSet.remove(row + "-" + column);
    }

    return conflictSet;
  }

  /**
   * 添加离焦监听 失去焦点后检测当前值是否冲突
   *
   * @param row    行
   * @param column 列
   * @return 焦点事件监听器
   */
  private FocusListener generateFocusListener(int row, int column) {
    return new FocusListener() {
      @Override
      public void focusGained(FocusEvent e) {
      }

      @Override
      public void focusLost(FocusEvent e) {
        // 文本框
        JTextField jTextField = (JTextField) e.getComponent();
        // 检测当前值是否冲突
        checkValue(row, column, jTextField.getText());
      }
    };
  }

  /**
   * 校验当前填的值是否有冲突，冲突就高亮处理
   *
   * @param row    行
   * @param column 列
   * @param text   待校验的值
   */
  private void checkValue(int row, int column, String text) {
    if ("".equals(text)) {
      return;
    }

    // 刷新检查网格
    refreshCheckInts();

    // 冲突集合[横坐标，纵坐标]
    Set<String> conflictSet = getConflictSet(checkGrid.toArray(), row, column, Integer.parseInt(text));

    // 冲突上色
    conflictSet.forEach(s -> {
      String[] xy = s.split("-");
      JTextField jTextField = chessBoard[Integer.parseInt(xy[0])][Integer.parseInt(xy[1])];
      jTextField.setForeground(theme.getTipForegroundReal());
      // 刷新界面
      jTextField.repaint();
    });

    // 还原成原来的颜色
    ThreadUtil.execAsync(() -> {
      ThreadUtils.spinMoment(3000);
      conflictSet.forEach(s -> {
        String[] xy = s.split("-");
        JTextField jTextField = chessBoard[Integer.parseInt(xy[0])][Integer.parseInt(xy[1])];
        jTextField.setForeground(colorMap.get(s));
        // 刷新界面
        jTextField.repaint();
      });
    });
  }
}
